package engine

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"strconv"
	"sync"

	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"flag"
	"net/url"

	//	"sync"
	//"time"

	"github.com/google/uuid"

	log "github.com/pion/ion-log"
	//pb "github.com/pion/ion-sfu/cmd/signal/grpc/proto"
	"github.com/pion/webrtc/v3"
	//"google.golang.org/grpc"
	//"google.golang.org/grpc/codes"
	//"google.golang.org/grpc/status"
	"github.com/gorilla/websocket"
)

// Signal is a wrapper of grpc
type Signal struct {
	id string
	//client pb.SFUClient
	//stream pb.SFU_SignalClient

	conn   *websocket.Conn
	client *Client

	OnNegotiate    func(webrtc.SessionDescription) error
	OnTrickle      func(candidate webrtc.ICECandidateInit, target int)
	OnSetRemoteSDP func(webrtc.SessionDescription) error
	OnError        func(error)

	ctx        context.Context
	cancel     context.CancelFunc
	handleOnce sync.Once
	sync.Mutex
}

// dir /x查看短路径
type Candidate struct {
	Target    int                  `json:"target"`
	Candidate *webrtc.ICECandidate `json:candidate`
}

type ResponseCandidate struct {
	Target    int                      `json:"target"`
	Candidate *webrtc.ICECandidateInit `json:candidate`
}

// SendOffer object to send to the sfu over Websockets
type SendOffer struct {
	SID   string                     `json:sid`
	Offer *webrtc.SessionDescription `json:offer`
}

// SendAnswer object to send to the sfu over Websockets
type SendAnswer struct {
	SID    string                     `json:sid`
	Answer *webrtc.SessionDescription `json:answer`
}

// TrickleResponse received from the sfu server
type TrickleResponse struct {
	Params ResponseCandidate `json:params`
	Method string            `json:method`
}

// Response received from the sfu over Websockets
type Response struct {
	Params *webrtc.SessionDescription `json:params`
	Result *webrtc.SessionDescription `json:result`
	Sparam string                     `json:sparam`
	Method string                     `json:method`
	Id     uint64                     `json:id`
}

// NewSignal create a grpc signaler
func NewSignal(addr, id string) (*Signal, error) {
	s := &Signal{}
	s.id = id
	/*
		// Set up a connection to the sfu server.
		ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
		defer cancel()
		conn, err := grpc.DialContext(ctx, addr, grpc.WithInsecure())
		if err != nil {
			log.Errorf("[%v] Connecting to sfu:%s failed: %v", s.id, addr, err)
			return nil, err
		}
		log.Infof("[%v] Connecting to sfu ok: %s", s.id, addr)

		s.ctx, s.cancel = context.WithCancel(context.Background())
		s.client = pb.NewSFUClient(conn)
		s.stream, err = s.client.Signal(s.ctx)
		if err != nil {
			log.Errorf("err=%v", err)
			return nil, err
		}
	*/

	flag.StringVar(&addr, "a", "localhost:7000", "address to use")
	flag.Parse()

	u := url.URL{Scheme: "ws", Host: addr, Path: "/ws"}
	//log.Printf("connecting to %s", u.String())

	c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
	if err != nil {
		//log.Fatal("dial:", err)
	}

	s.conn = c

	return s, nil
}

/*
func (s *Signal) onSignalHandleOnce() {
	// onSignalHandle is wrapped in a once and only started after another public
	// method is called to ensure the user has the opportunity to register handlers
	s.handleOnce.Do(func() {
		err := s.onSignalHandle()
		if s.OnError != nil {
			s.OnError(err)
		}
	})
}
*/

func (s *Signal) onSignalHandle() error {

	connection := s.conn
	for {
		_, message, err := connection.ReadMessage()
		if err != nil || err == io.EOF {
			//log.Fatal("Error reading: ", err)
			break
		}

		//fmt.Printf("recv: %s", message)

		var response Response
		json.Unmarshal(message, &response)

		if response.Method == "publickey" {
			//login
			//https://blog.csdn.net/qq_21794823/article/details/79817733
			//https://www.jianshu.com/p/f56d45ea02b1
			connectionUUID := uuid.New()
			connUID := uint64(connectionUUID.ID())
			connSID := strconv.FormatUint(connUID, 10)
			go func(respons Response) {
				//rsa.EncryptPKCS1v15(rand.Reader, privateKey, ciphertext)
				pubkey := respons.Sparam
				block, _ := pem.Decode([]byte(pubkey))
				pbKey, err := x509.ParsePKIXPublicKey(block.Bytes)
				//pbKey, err := x509.ParsePKIXPublicKey([]byte(pubkey))
				if err != nil {
					fmt.Println("ParsePKCS1PublicKey:", err.Error())
					return
				}

				rand.Prime(rand.Reader, 8)
				publicKey := pbKey.(*rsa.PublicKey)
				fmt.Printf("pubkey: %s", pubkey)
				fmt.Println("pubkey:", publicKey.Size())
				//username := "username"
				//passwd := "passwd"
				_username, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, []byte("username"))
				_passwd, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, []byte("passwd"))
				username := base64.StdEncoding.EncodeToString(_username)
				passwd := base64.StdEncoding.EncodeToString(_passwd)
				fmt.Println(string(username), "----", string(passwd))
				connection.WriteMessage(websocket.TextMessage, []byte(`{"method":"login","username":"`+string(username)+`","passwd":"`+string(passwd)+`","params":"","id":`+connSID+`}`))
			}(response)
		} else if response.Method == "login" {
			fmt.Println(response.Params)
			//go PeerConn(connection)
			//go s.client.Join("id", "uid", "offer")
			go s.client.Join("testroom")
		}
		/*
			 else if response.Id == connectionID {
				result := *response.Result
				remoteDescription = response.Result
				if err := peerConnection.SetRemoteDescription(result); err != nil {
					log.Fatal(err)
				}
			} else if response.Id != 0 && response.Method == "offer" {
				peerConnection.SetRemoteDescription(*response.Params)
				answer, err := peerConnection.CreateAnswer(nil)

				if err != nil {
					log.Fatal(err)
				}

				peerConnection.SetLocalDescription(answer)

				connectionUUID := uuid.New()
				connectionID = uint64(connectionUUID.ID())

				offerJSON, err := json.Marshal(&SendAnswer{
					Answer: peerConnection.LocalDescription(),
					SID:    "test room",
				})

				params := (*json.RawMessage)(&offerJSON)

				answerMessage := &jsonrpc2.Request{
					Method: "answer",
					Params: params,
					ID: jsonrpc2.ID{
						IsString: false,
						Str:      "",
						Num:      connectionID,
					},
				}

				reqBodyBytes := new(bytes.Buffer)
				json.NewEncoder(reqBodyBytes).Encode(answerMessage)

				messageBytes := reqBodyBytes.Bytes()
				connection.WriteMessage(websocket.TextMessage, messageBytes)
			} else if response.Method == "trickle" {
				var trickleResponse TrickleResponse

				if err := json.Unmarshal(message, &trickleResponse); err != nil {
					log.Fatal(err)
				}

				err := peerConnection.AddICECandidate(*trickleResponse.Params.Candidate)

				if err != nil {
					log.Fatal(err)
				}
			}
		*/
	}
	return nil
}

/*
func (s *Signal) onSignalHandle() error {
	for {
		//only one goroutine for recving from stream, no need to lock
		res, err := s.stream.Recv()
		if err != nil {
			if err == io.EOF {
				log.Infof("[%v] WebRTC Transport Closed", s.id)
				if err := s.stream.CloseSend(); err != nil {
					log.Errorf("[%v] error sending close: %s", s.id, err)
				}
				return err
			}

			errStatus, _ := status.FromError(err)
			if errStatus.Code() == codes.Canceled {
				if err := s.stream.CloseSend(); err != nil {
					log.Errorf("[%v] error sending close: %s", s.id, err)
				}
				return err
			}

			log.Errorf("[%v] Error receiving signal response: %v", s.id, err)
			return err
		}

		switch payload := res.Payload.(type) {
		case *pb.SignalReply_Join:
			// Set the remote SessionDescription
			log.Infof("[%v] [join] got answer: %s", s.id, payload.Join.Description)

			var sdp webrtc.SessionDescription
			err := json.Unmarshal(payload.Join.Description, &sdp)
			if err != nil {
				log.Errorf("[%v] [join] sdp unmarshal error: %v", s.id, err)
				return err
			}

			if err = s.OnSetRemoteSDP(sdp); err != nil {
				log.Errorf("[%v] [join] s.OnSetRemoteSDP error %s", s.id, err)
				return err
			}
		case *pb.SignalReply_Description:
			var sdp webrtc.SessionDescription
			err := json.Unmarshal(payload.Description, &sdp)
			if err != nil {
				log.Errorf("[%v] [description] sdp unmarshal error: %v", s.id, err)
				return err
			}
			if sdp.Type == webrtc.SDPTypeOffer {
				log.Infof("[%v] [description] got offer call s.OnNegotiate sdp=%+v", s.id, sdp)
				err := s.OnNegotiate(sdp)
				if err != nil {
					log.Errorf("err=%v", err)
				}
			} else if sdp.Type == webrtc.SDPTypeAnswer {
				log.Infof("[%v] [description] got answer call s.OnSetRemoteSDP sdp=%+v", s.id, sdp)
				err = s.OnSetRemoteSDP(sdp)
				if err != nil {
					log.Errorf("[%v] [description] s.OnSetRemoteSDP err=%s", s.id, err)
				}
			}
		case *pb.SignalReply_Trickle:
			var candidate webrtc.ICECandidateInit
			_ = json.Unmarshal([]byte(payload.Trickle.Init), &candidate)
			log.Infof("[%v] [trickle] type=%v candidate=%v", s.id, payload.Trickle.Target, candidate)
			s.OnTrickle(candidate, int(payload.Trickle.Target))
		default:
			// log.Errorf("Unknow signal type!!!!%v", payload)
		}
	}
}
*/

func (s *Signal) Join(sid string, uid string, offer webrtc.SessionDescription) error {
	log.Infof("[%v] [Signal.Join] sid=%v offer=%v", s.id, sid, offer)
	// marshalled, err := json.Marshal(offer)
	// if err != nil {
	// 	return err
	// }
	//go s.onSignalHandleOnce()
	s.Lock()
	/*
		err = s.stream.Send(
			&pb.SignalRequest{
				Payload: &pb.SignalRequest_Join{
					Join: &pb.JoinRequest{
						Sid:         sid,
						Uid:         uid,
						Description: marshalled,
					},
				},
			},
		)
	*/
	err := s.conn.WriteJSON(offer)
	s.Unlock()
	if err != nil {
		log.Errorf("[%v] err=%v", s.id, err)
	}
	return err
}

func (s *Signal) Trickle(candidate *webrtc.ICECandidate, target int) {
	log.Infof("[%v] [Signal.Trickle] candidate=%v target=%v", s.id, candidate, target)
	// bytes, err := json.Marshal(candidate.ToJSON())
	// if err != nil {
	// 	log.Errorf("err=%v", err)
	// 	return
	// }
	//go s.onSignalHandleOnce()
	s.Lock()
	/*
		err = s.stream.Send(&pb.SignalRequest{
			Payload: &pb.SignalRequest_Trickle{
				Trickle: &pb.Trickle{
					Init:   string(bytes),
					Target: pb.Trickle_Target(target),
				},
			},
		})
	*/

	err := s.conn.WriteJSON(candidate)
	s.Unlock()
	if err != nil {
		log.Errorf("[%v] err=%v", s.id, err)
	}
}

func (s *Signal) Offer(sdp webrtc.SessionDescription) {
	log.Infof("[%v] [Signal.Offer] sdp=%v", s.id, sdp)
	// marshalled, err := json.Marshal(sdp)
	// if err != nil {
	// 	log.Errorf("[%v] err=%v", s.id, err)
	// 	return
	// }
	//go s.onSignalHandleOnce()
	s.Lock()
	/*
		err = s.stream.Send(
			&pb.SignalRequest{
				Payload: &pb.SignalRequest_Description{
					Description: marshalled,
				},
			},
		)
	*/
	err := s.conn.WriteJSON(sdp)
	s.Unlock()
	if err != nil {
		log.Errorf("[%v] err=%v", s.id, err)
	}
}

func (s *Signal) Answer(sdp webrtc.SessionDescription) {
	log.Infof("[%v] [Signal.Answer] sdp=%v", s.id, sdp)
	// marshalled, err := json.Marshal(sdp)
	// if err != nil {
	// 	log.Errorf("err=%v", err)
	// 	return
	// }
	s.Lock()
	// err = s.stream.Send(
	// 	&pb.SignalRequest{
	// 		Payload: &pb.SignalRequest_Description{
	// 			Description: marshalled,
	// 		},
	// 	},
	// )

	err := s.conn.WriteJSON(sdp)
	s.Unlock()
	if err != nil {
		log.Errorf("[%v] err=%v", s.id, err)
	}
}

func (s *Signal) Close() {
	log.Infof("[%v] [Signal.Close]", s.id)
	s.cancel()
	//go s.onSignalHandleOnce()
	go s.onSignalHandle()
}
